package hk.edu.polyu.comp446.group5;

import javax.swing.JOptionPane;

public class FinancialCore {
	private int statAdjustSize = 0;
	private double initInvest = 1.0;
	private double quantile = 0.05;
	private static double RISK_FREE_RATE = 0.0004167;

	Financial stockStatisticsData = new Financial();

	// Default Constructor
	public FinancialCore(String returnsType) {
		this.stockStatisticsData.setReturnsType(returnsType);
	}

	public double[] returnsType(double array[]) {
		double[] returnArry = new double[array.length - 1];
		int k = 0;
		int n = 0;
		if (this.stockStatisticsData.getReturnsType() == "SimpleReturn") {
			for (int i = 0; i < array.length - 1; i++) {
				n = i + 1;
				returnArry[k] = ((array[n] - array[i]) / array[i]);
				//System.out.println(ccReturnArry[k]);  // DEBUG USE ONLY
				k++;
			}
		} else {
			for (int i = 0; i < array.length - 1; i++) {
				n = i + 1;
				returnArry[k] = Math.log(array[n] / array[i]);
				//System.out.println(ccReturnArry[k]);  // DEBUG USE ONLY
				k++;
			}
		}
		return returnArry;
	}

	// Mean
	public Financial Mean(double[] data) {
		double[] ccReturn = returnsType(data); // Convert Price to Continuing Compounding Return
		statAdjustSize = (ccReturn.length);
		double sum = 0.0;
		double mean = 0.0;
		for (double r : ccReturn) {
			sum += r;
		}
		mean = sum / statAdjustSize;
		stockStatisticsData.setMean(mean);
		return (stockStatisticsData);
	}

	// Variance
	public Financial Variance(double[] data) {
		double[] ccReturn = new double[data.length - 1];
		ccReturn = returnsType(data); // Convert Price to Continuing Compounding Return
		double mean = Mean(data).getMean(); // Pass non-cc return data
		double var_temp = 0;
		for (double r : ccReturn) {
			var_temp += (r - mean) * (r - mean); // Square
		}
		statAdjustSize = (ccReturn.length - 1); // Count of Data - 1 : For statistics reason to adjust the estimation of mean: It's called Bessel's correction http://en.wikipedia.org/wiki/Bessel%27s_correction
		stockStatisticsData.setVariance(var_temp / statAdjustSize);
		return (stockStatisticsData);
	}

	// Standard Deviation
	public Financial StdDev(double[] data) {
		double variance = this.Variance(data).getVariance();
		stockStatisticsData.setStdDeviation(Math.sqrt(variance));
		return (stockStatisticsData);
	}

	// Skewness
	public Financial Skewness(double[] data) {
		double[] ccReturn = new double[data.length - 1];
		ccReturn = returnsType(data); // Convert Price to Continuing Compounding Return 
		double std = StdDev(data).getStdDeviation();
		double tmpSkew = 0.0;
		statAdjustSize = (ccReturn.length - 1); // Count of Data - 1 : For statistics reason to adjust the estimation of mean: It's called Bessel's correction http://en.wikipedia.org/wiki/Bessel%27s_correction	
		/*
		 * Calculate Method: 1. [(C.C. Return - Mean) ^ 3] / Count of C.C. Returns 2. Sum all of (Item 1) 3. (Item 2) / (Standard Deviation ^ 3)
		 */
		for (double a : ccReturn) {
			tmpSkew += Math.pow((a - Mean(data).getMean()), 3) / statAdjustSize;
		}
		stockStatisticsData.setSkew(tmpSkew / Math.pow(std, 3));
		return (stockStatisticsData);
	}

	// Kurtosis
	public Financial Kurtosis(double[] data) {
		double[] ccReturn = new double[data.length - 1];
		ccReturn = returnsType(data); // Convert Price to Continuing Compounding Return 
		double std = StdDev(data).getStdDeviation();
		double tmpKurt = 0.0;
		statAdjustSize = (ccReturn.length - 1); // Count of Data - 1 : For statistics reason to adjust the estimation of mean: It's called Bessel's correction http://en.wikipedia.org/wiki/Bessel%27s_correction	
		/*
		 * Calculate Method: 1. [(C.C. Return - Mean) ^ 4] / Count of C.C. Returns 2. Sum all of (Item 1) 3. (Item 2) / (Standard Deviation ^ 4)
		 */
		for (double a : ccReturn) {
			tmpKurt += Math.pow((a - Mean(data).getMean()), 4) / statAdjustSize;
		}
		stockStatisticsData.setKurtosis((tmpKurt / Math.pow(std, 4)) - 3); // Excess Kurtosis
		return (stockStatisticsData);
	}

	// Value-at-Risk
	public Financial ValueAtRisk(double[] data) {
		double mean = Mean(data).getMean();
		double std = StdDev(data).getStdDeviation();
		stockStatisticsData.setValueAtRisk(mean + std * Statistics.NormSInv(quantile, true));
		return (stockStatisticsData);
	}

	// Value-at-Risk
	public Financial ValueAtRisk(double[] data, double initInvest, double quantile) {
		double mean = Mean(data).getMean();
		double std = StdDev(data).getStdDeviation();
		double vaR = 0.0;
		try {
			quantile = Statistics.NormInv(quantile, mean, std);
			vaR = (mean + quantile * std) * initInvest;
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		stockStatisticsData.setValueAtRisk(vaR);
		return (stockStatisticsData);
	}

	// Sharpe Ratio
	public Financial SharpeRatio(double[] data) {
		double mean = Mean(data).getMean();
		double std = StdDev(data).getStdDeviation();
		stockStatisticsData.setSharpeRatio((mean - RISK_FREE_RATE) / std);
		return (stockStatisticsData);
	}

	/*
	 * ================================********* Portfolio ********** =================================
	 */

	// Covariance
	public Financial Covariance(double[] assetA, double[] assetB) {
		assetA = returnsType(assetA); // Convert Price to Continuing Compounding Return
		assetB = returnsType(assetB); // Convert Price to Continuing Compounding Return
		double covariance = 0.0;

		// Check the length of two asset are the same
		if (assetA.length == assetB.length) {
			int size = assetA.length;
			double[][] asset = new double[2][size];
			double sumAssetA = 0.0;
			double sumAssetB = 0.0;
			double meanAssetA = 0.0;
			double meanAssetB = 0.0;

			int i = 0;
			int j = 0;

			// Calculate the Total of Asset A
			for (double a : assetA) {
				asset[0][i] = a;
				sumAssetA += a;
				//System.out.println("AssetA: " + i + " " + asset[0][i]);  // Debug use only
				i++;
			}

			// Calculate the Total of Asset B
			for (double b : assetB) {
				asset[1][j] = b;
				sumAssetB += b;
				//System.out.println("AssetB: " + j + " " + asset[1][j]);  // Debug use only
				j++;
			}

			meanAssetA = (sumAssetA / size);
			meanAssetB = (sumAssetB / size);

			for (i = 0; i < size; i++) {
				covariance += (asset[0][i] - meanAssetA) * (asset[1][i] - meanAssetB);
				//System.out.println("Covariance: " + i + " " + covariance);  // Debug use only
				if (i == size - 1) {
					//System.out.println("Covariance: " + i + " " + covariance);  // Debug use only
					//System.out.println("==================END==================");  // Debug use only
					covariance = covariance / (i);
				}
			}
			stockStatisticsData.setCovariance(covariance);
		} else {
			System.out.println("Asset A and Asset B are not Time dependent");
		}
		return (stockStatisticsData);
	}

	// Correlation
	public Financial Correlation(double[] assetA, double[] assetB) {
		double covariance = this.Covariance(assetA, assetB).getCovariance();
		double stdDevAssetA = this.StdDev(assetA).getStdDeviation();
		double stdDevAssetB = this.StdDev(assetB).getStdDeviation();
		double correlation = 0.0;
		correlation = (covariance / (stdDevAssetA * stdDevAssetB));
		stockStatisticsData.setCorrelation(correlation);
		return (stockStatisticsData);
	}

	// Portfolio Mean
	public Financial Mean(double[] assetA, double[] assetB, double allocateA, double allocateB) {
		double meanA = this.Mean(assetA).getMean();
		double meanB = this.Mean(assetB).getMean();
		double pMean = 0.0;
		if (allocateA + allocateB == 1) {
			pMean = (allocateA * meanA) + (allocateB * meanB);
		} else {
			System.out.println("Allocate Rate Incorrect!!!");
		}
		stockStatisticsData.setMean(pMean);
		return stockStatisticsData;
	}

	// Portfolio Variance
	public Financial Variance(double[] assetA, double[] assetB, double allocateA, double allocateB) {
		double varA = this.Variance(assetA).getVariance();
		double varB = this.Variance(assetB).getVariance();
		double cov = this.Covariance(assetA, assetB).getCovariance();
		double pVariance = 0.0;

		if (allocateA + allocateB == 1) {
			pVariance = (Math.pow(allocateA, 2) * varA) + (Math.pow(allocateB, 2) * varB) + (2 * allocateA * allocateB * cov);
		} else {
			System.out.println("Allocate Rate Incorrect!!!");
		}
		stockStatisticsData.setVariance(pVariance);
		return stockStatisticsData;
	}

	// Portfolio Standard Deviation
	public Financial StdDev(double[] assetA, double[] assetB, double allocateA, double allocateB) {
		double stdDeviation = Math.sqrt(this.Variance(assetA, assetB, allocateA, allocateB).getVariance());
		stockStatisticsData.setStdDeviation(stdDeviation);
		return stockStatisticsData;
	}

	// Portfolio Sharpe Ratio
	public Financial SharpeRatio(double[] assetA, double[] assetB, double allocateA, double allocateB) {
		double mean = this.Mean(assetA, assetB, allocateA, allocateB).getMean();
		double stdDeviation = this.StdDev(assetA, assetB, allocateA, allocateB).getStdDeviation();
		double sharpeRatio = 0.0;

		if (allocateA + allocateB == 1) {
			sharpeRatio = (mean - RISK_FREE_RATE) / stdDeviation;
		} else {
			System.out.println("Allocate Rate Incorrect!!!");
		}
		stockStatisticsData.setSharpeRatio(sharpeRatio);
		return stockStatisticsData;
	}

	// Portfolio Value-at-Risk
	public Financial ValueAtRisk(double[] assetA, double[] assetB, double allocateA, double allocateB) {
		double valueAtRisk = 0.0;
		double mean = this.Mean(assetA, assetB, allocateA, allocateB).getMean();
		double std = this.StdDev(assetA, assetB, allocateA, allocateB).getStdDeviation();
		if (allocateA + allocateB == 1) {
			try {
				//quantile = Statistics.NormInv(quantile, mean, std);
				quantile = Statistics.NormSInv(quantile, true);
				valueAtRisk = (mean + quantile * std) * initInvest;
			} catch (Throwable e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else {
			System.out.println("Allocate Rate Incorrect!!!");
		}
		stockStatisticsData.setValueAtRisk(valueAtRisk);
		return stockStatisticsData;
	}

	// Portfolio Value-at-Risk
	public Financial ValueAtRisk(double[] assetA, double[] assetB, double allocateA, double allocateB, double initInvest, double quantile) {
		double valueAtRisk = 0.0;
		double mean = this.Mean(assetA, assetB, allocateA, allocateB).getMean();
		double std = this.StdDev(assetA, assetB, allocateA, allocateB).getStdDeviation();
		if (allocateA + allocateB == 1) {
			try {
				//quantile = Statistics.NormInv(quantile, mean, std);
				quantile = Statistics.NormSInv(quantile, true);
				//valueAtRisk = (1 + (mean + quantile * std)) * initInvest;
				valueAtRisk = (mean + quantile * std) * initInvest;
			} catch (Throwable e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else {
			System.out.println("Allocate Rate Incorrect!!!");
		}
		stockStatisticsData.setValueAtRisk(valueAtRisk);
		return stockStatisticsData;
	}

	// Portfolio Global Minimum Variance Portfolio
	public Financial GlobalMinVarPortfolioA(double[] assetA, double[] assetB) {
		double varA = this.Variance(assetA).getVariance();
		double varB = this.Variance(assetB).getVariance();
		double cov = this.Covariance(assetA, assetB).getCovariance();
		double globalMin = 0.0;
		try {
			globalMin = (varB - cov) / (varA + varB - (2 * cov));
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		stockStatisticsData.setGlobalMin(globalMin);
		return stockStatisticsData;
	}

	// Portfolio Global Minimum Variance Portfolio
	public Financial GlobalMinVarPortfolioB(double[] assetA, double[] assetB) {
		double globalMin = 0.0;
		globalMin = 1 - this.GlobalMinVarPortfolioA(assetA, assetB).getGlobalMin();
		stockStatisticsData.setGlobalMin(globalMin);
		return stockStatisticsData;
	}

	// Portfolio Tangency PortfolioA
	public Financial TangencyPortfolioA(double[] assetA, double[] assetB) {
		double meanA = this.Mean(assetA).getMean();
		double meanB = this.Mean(assetB).getMean();
		double varA = this.Variance(assetA).getVariance();
		double varB = this.Variance(assetB).getVariance();
		double cov = this.Covariance(assetA, assetB).getCovariance();
		double tangency = 0.0;
		try {
			tangency = (((meanA - RISK_FREE_RATE) * varB) - ((meanB - RISK_FREE_RATE) * cov))
					/ (((meanA - RISK_FREE_RATE) * varB) + ((meanB - RISK_FREE_RATE) * varA) - (meanA - RISK_FREE_RATE + meanB - RISK_FREE_RATE) * cov);
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		stockStatisticsData.setTangency(tangency);
		return stockStatisticsData;
	}

	// Portfolio Tangency PortfolioB
	public Financial TangencyPortfolioB(double[] assetA, double[] assetB) {
		double tangency = 0.0;
		tangency = 1.0 - this.TangencyPortfolioA(assetA, assetB).getTangency();
		stockStatisticsData.setTangency(tangency);
		return stockStatisticsData;
	}

	// Portfolio Efficient Portfolios given Expected Return
	public Financial EfficientPortfoliosRiskA(double[] assetA, double[] assetB, double expectedReturn) {
		double meanA = this.Mean(assetA).getMean();
		double meanB = this.Mean(assetB).getMean();
		double efficient = 0.0;

		try {
			efficient = (expectedReturn - meanB) / (meanA - meanB);
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		stockStatisticsData.setEfficientPortfolio(efficient);
		return stockStatisticsData;
	}

	// Portfolio Efficient Portfolios given Expected Return
	public Financial EfficientPortfoliosRiskB(double[] assetA, double[] assetB, double expectedReturn) {
		double efficient = 1 - this.EfficientPortfoliosRiskA(assetA, assetB, expectedReturn).getEfficientPortfolio();
		stockStatisticsData.setEfficientPortfolio(efficient);
		return stockStatisticsData;
	}

	// Portfolio Efficient Portfolios given Expected Return
	public Financial EfficientPortfoliosRisk(double[] assetA, double[] assetB, double expectedReturn) {
		double varA = this.Variance(assetA).getVariance();
		double varB = this.Variance(assetB).getVariance();
		double cov = this.Covariance(assetA, assetB).getCovariance();
		double efficientA = this.EfficientPortfoliosRiskA(assetA, assetB, expectedReturn).getEfficientPortfolio();
		double efficientB = this.EfficientPortfoliosRiskB(assetA, assetB, expectedReturn).getEfficientPortfolio();
		double risk = 0.0;
		try {
			risk = (Math.pow(efficientA, 2) * varA) + (Math.pow(efficientB, 2) * varB) + (2 * efficientA * efficientB * cov);
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		if (efficientA < 0 || efficientB < 0)
			System.out.println("Short Sell");
		stockStatisticsData.setEfficientPortfolioRisk(risk);
		return stockStatisticsData;
	}

	// Portfolio Efficient Portfolios given Expected Risk
	public Financial EfficientPortfoliosReturnA(double[] assetA, double[] assetB, double expectedRisk) {
		double varA = this.Variance(assetA).getVariance();
		double varB = this.Variance(assetB).getVariance();
		double cov = this.Covariance(assetA, assetB).getCovariance();
		double a = 0.0;
		double b = 0.0;
		double c = 0.0;
		double efficient = 0.0;
		try {
			a = varA + varB - (2 * cov);
			b = (2 * (cov - varB));
			c = varB - (Math.pow(expectedRisk, 2));
			efficient = ((-b) + Math.sqrt((Math.pow(b, 2)) - 4 * a * c)) / (2 * a);
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		stockStatisticsData.setEfficientPortfolio(efficient);
		return stockStatisticsData;
	}

	// Portfolio Efficient Portfolios given Expected Risk
	public Financial EfficientPortfoliosReturnB(double[] assetA, double[] assetB, double expectedRisk) {
		double efficient = 1 - this.EfficientPortfoliosReturnA(assetA, assetB, expectedRisk).getEfficientPortfolio();
		stockStatisticsData.setEfficientPortfolio(efficient);
		return stockStatisticsData;
	}

	// Portfolio Efficient Portfolios given Expected Risk
	public Financial EfficientPortfoliosReturn(double[] assetA, double[] assetB, double expectedRisk) {
		double meanA = this.Mean(assetA).getMean();
		double meanB = this.Mean(assetB).getMean();
		double effA = this.EfficientPortfoliosReturnA(assetA, assetB, expectedRisk).getEfficientPortfolio();
		double effB = this.EfficientPortfoliosReturnB(assetA, assetB, expectedRisk).getEfficientPortfolio();
		double efficient = 0.0;
		
		try {
			efficient = (effA * meanA) + (effB * meanB);
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		stockStatisticsData.setEfficientPortfolioReturn(efficient);
		return stockStatisticsData;
	}
}